# -*- coding: utf-8 -*-
# @Time    : 2024/6/11 17:37
# @Author  : yujiahao
# @File    : 14_funcation_parameter.py
# @description:Python中的函数 -- 参数篇


"""
Java, Scala 和 Python 中函数的对比，包括定义、语法格式、参数传递、返回值和匿名函数（lambda 表达式）。

Java

函数定义与调用
在 Java 中，函数（方法）必须定义在类中，不能单独存在。方法可以是实例方法或静态方法。

语法结构：
返回类型 方法名(参数列表) {
    方法体
}

示例：
public class Example {
    // 静态方法
    public static int add(int a, int b) {
        return a + b;
    }

    // 实例方法
    public int multiply(int a, int b) {
        return a * b;
    }

    public static void main(String[] args) {
        // 调用静态方法
        int result1 = Example.add(5, 3);
        System.out.println("Addition: " + result1);

        // 调用实例方法
        Example example = new Example();
        int result2 = example.multiply(5, 3);
        System.out.println("Multiplication: " + result2);
    }
}

参数传递
Java 采用值传递（pass-by-value）的方式进行参数传递，对于基本数据类型传递的是值，对于对象传递的是引用的副本。

示例：
public class ParameterExample {
    public static void modifyValue(int value) {
        value = 10;
    }

    public static void modifyObject(MyObject obj) {
        obj.value = 10;
    }

    public static void main(String[] args) {
        int value = 5;
        modifyValue(value);
        System.out.println("Value: " + value);  // 输出 5

        MyObject obj = new MyObject();
        obj.value = 5;
        modifyObject(obj);
        System.out.println("Object value: " + obj.value);  // 输出 10
    }
}

class MyObject {
    int value;
}

返回值
Java 方法可以返回任意类型，包括基本类型、对象或 void（无返回值）。

示例：
public class ReturnExample {
    public static int getInt() {
        return 5;
    }

    public static String getString() {
        return "Hello";
    }

    public static void printMessage() {
        System.out.println("Hello, World!");
    }

    public static void main(String[] args) {
        int num = getInt();
        String message = getString();
        printMessage();
    }
}

匿名函数（Lambda 表达式）【flink或者spark经常用】
Java 8 引入了 lambda 表达式，用于简化匿名类的定义。

语法结构：
(参数列表) -> { 方法体 }

示例：
import java.util.function.*;

public class LambdaExample {
    public static void main(String[] args) {
        // 使用 lambda 表达式实现函数接口
        Function<Integer, Integer> square = x -> x * x;
        System.out.println("Square of 4: " + square.apply(4));

        // 使用 lambda 表达式实现自定义接口
        MyFunction add = (a, b) -> a + b;
        System.out.println("Addition: " + add.apply(5, 3));
    }

    @FunctionalInterface
    interface MyFunction {
        int apply(int a, int b);
    }
}

Scala

函数定义与调用
Scala 支持在类、对象或任何地方定义函数。函数可以是方法或函数值（函数对象）。

语法结构：
def 方法名(参数列表): 返回类型 = {
    方法体
}

示例：
object Example {
    // 方法
    def add(a: Int, b: Int): Int = {
        a + b
    }

    // 函数值
    val multiply: (Int, Int) => Int = (a, b) => a * b

    def main(args: Array[String]): Unit = {
        // 调用方法
        val result1 = add(5, 3)
        println(s"Addition: $result1")

        // 调用函数值
        val result2 = multiply(5, 3)
        println(s"Multiplication: $result2")
    }
}

参数传递
Scala 也是采用值传递（pass-by-value）的方式进行参数传递。对于引用类型，传递的是引用的副本。

示例：
object ParameterExample {
    def modifyValue(value: Int): Unit = {
        value = 10
    }

    def modifyObject(obj: MyObject): Unit = {
        obj.value = 10
    }

    def main(args: Array[String]): Unit = {
        var value = 5
        modifyValue(value)
        println(s"Value: $value")  // 输出 5

        val obj = new MyObject
        obj.value = 5
        modifyObject(obj)
        println(s"Object value: ${obj.value}")  // 输出 10
    }

    class MyObject {
        var value: Int = _
    }
}

返回值
Scala 函数可以返回任意类型，包括基本类型、对象或 Unit（无返回值）。

示例：
object ReturnExample {
    def getInt(): Int = 5

    def getString(): String = "Hello"

    def printMessage(): Unit = {
        println("Hello, World!")
    }

    def main(args: Array[String]): Unit = {
        val num = getInt()
        val message = getString()
        printMessage()
    }
}

匿名函数（Lambda 表达式）【这个很有用，有兴趣的可以了解一下Scala函数式编程，val add = (_ + _)   这个函数表示输入两个参数相加】
Scala 支持使用 lambda 表达式来定义匿名函数。

语法结构：
(参数列表) => 方法体

示例：
object LambdaExample {
    def main(args: Array[String]): Unit = {
        // 使用 lambda 表达式
        val square: Int => Int = x => x * x
        println(s"Square of 4: ${square(4)}")

        // 使用 lambda 表达式实现自定义函数
        val add: (Int, Int) => Int = (a, b) => a + b
        println(s"Addition: ${add(5, 3)}")
    }
}

Python

函数定义与调用
Python 支持在任何地方定义函数，函数可以是全局函数或嵌套函数。

语法结构：
def 函数名(参数列表):
    函数体

示例：
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

if __name__ == "__main__":
    result1 = add(5, 3)
    print(f"Addition: {result1}")

    result2 = multiply(5, 3)
    print(f"Multiplication: {result2}")

参数传递
Python 采用值传递（pass-by-value）的方式进行参数传递，对于可变对象传递的是引用。

示例：
def modify_value(value):
    value = 10

def modify_object(obj):
    obj['value'] = 10

if __name__ == "__main__":
    value = 5
    modify_value(value)
    print(f"Value: {value}")  # 输出 5

    obj = {'value': 5}
    modify_object(obj)
    print(f"Object value: {obj['value']}")  # 输出 10

返回值
Python 函数可以返回任意类型，包括基本类型、对象或 None（无返回值）。

示例：
def get_int():
    return 5

def get_string():
    return "Hello"

def print_message():
    print("Hello, World!")

if __name__ == "__main__":
    num = get_int()
    message = get_string()
    print_message()

匿名函数（Lambda 表达式）
Python 使用 lambda 表达式来定义匿名函数。

语法结构：
lambda 参数列表: 表达式

示例：
square = lambda x: x * x
print(f"Square of 4: {square(4)}")

add = lambda a, b: a + b
print(f"Addition: {add(5, 3)}")
"""

# todo  1、函数定义

'''
语法结构：
def 函数名(参数列表):
    函数体
       
 - def: 是python中关键字主要用来定义函数的
 - 函数名:这个是函数的名字
 - ()这个括号是个神奇的东西,咱们后边会详细盘它
 - : 冒号是表示咱们这个语句写完了
 - 函数体就是有4个空格的缩进(python 缩进很重要)
 
 
 
 当我们调用执行的时候,才会执行func这个空间里的代码,执行的时候在开辟空间,这次是在func里边开辟的空间：
 
 执行过程：
    第一步：a 被分配字符串 "alexdsb"，count 被初始化为 0。
    第二步：进入 for 循环，遍历字符串 a 的每个字符。
    第一次循环：count 变为 1，打印 1。
    第二次循环：count 变为 2，打印 2。
    第三次循环：count 变为 3，打印 3。
    第四次循环：count 变为 4，打印 4。
    第五次循环：count 变为 5，打印 5。
    第六次循环：count 变为 6，打印 6。
    第七次循环：count 变为 7，打印 7。
 
'''


# 下面的函数就是计算字符串长度的
def len():
    a = "alexdsb"
    count = 0
    for i in a:
        count += 1
    print(count)


# 函数调用
len()

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 2、函数的返回值

'''函数中遇到return,此函数就立马结束了.不在继续执行

【函数执行过程】
    1.定义了一个函数yue
    2.调用函数
    3.执行函数里的约你
    4.执行函数里的约我
    5.执行函数里的约他
    6.返回给调用者一个字符串,这个调用者就是yue()
    7.将返回的字符串通过一个等号赋值给白变量girl
    8.将结果转换为tuple
    9.打印变量girl的类型

总结
　　1.遇到return,此函数结束,return下面的代码将不会在执行
　　2.return 返回值

　　　　关于返回值:
　　　　　　- 如果return什么都不写或者干脆就没写return,返回的结果就是None
　　　　　　- 如果return后面写了一个值,返回给调用者这个值
　　　　　　- 如果return后面写了多个结果,返回给调用者一个tuple(元祖),调用者可以直接使用解构获取多个
'''


def yue():
    print("约你")
    print("约我")
    print("约他")
    return "美女一枚", "萝莉一枚"


girl = yue()
print(type(girl))  # tuple

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 3、函数的参数
'''
参数,也就是函数括号里的内容 函数在调用的时候指定一个具体的变量的值 就是参数，另外 如果我们再定函数的时候写了形参,在调用函数的时候没有传递值,调用的时候右边括号会发黄,所以我们必须要传递参数,参数要一一对应,不能多不能少
    【形参】：写在函数声明的位置的变量叫形参,形式上的一个完整.表示这个函数需要xxx
    【实参】：在函数调用的时候给函数传递的值.加实参,实际执行的时候给函数传递的信息.表示给函数xxx
    【传参】：从调用函数的时候将值传递到定义函数的过程叫做传参
    【位置参数】在访问函数的时候,我们按照位置的顺序分别把参数赋值给了对应的参数,在传参过程中.系统会按照位置把实参赋值到形参.
     
     【形参就是一个变量名,实参就是值 传参就是赋值的过程】
    
'''


# 3.1 位置参数

def yue(chat, addr, age):  # chat  形参
    '''
    分析: 在访问yue()的时候,我们按照位置的顺序分别把"陌陌","北京",18赋值给了chat,addr,age,在传参过程中.系统会按照位置把实参赋值到形参.
    :param chat:
    :param addr:
    :param age:
    :return:
    '''

    print(f"拿出手机\n打开{chat}\n找个{addr}附近漂亮的{age}岁妹子\n约不约")


yue("陌陌", "北京", 18)  # 实参


# 练习：编写函数,给函数传递两个参数a,b 比较a,b的大小 返回a,b中最大的那个数
def f(a, b):
    '''
    :param a: 参数 a
    :param b:参数 b
    :return: 返回a,b中最大的那个数
    '''
    # 三元运算符  Java中是 a>b?a:b
    c = a if a > b else b  # 当a>b就把a赋值给c,否则就把b赋值给c

    return c


msg = f(5, 7)

print(msg)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 3.2 关键字参数

'''
位置参数好不好呢? 如果是少量的参数还算OK, 没有问题. 但是如果函数在定义的时候参数非常多怎么办? 
程序员必须记住, 我有哪些参数, 而且还有记住每个参数的位置, 否则函数就不能正常调用了. 
那则么办呢? python提出了一种叫做关键字参数. 我们不需要记住每个参数的位置. 只要记住每个参数的名字就可以了
'''


def yue(chat, address, age):
    print(f"拿出手机\n打开{chat}\n找个{address}附近漂亮的{age}岁妹子\n约不约")


# 调用的时候可以直接指定一下
yue(chat="微信", age=18, address="北京")  # 关键字参数.

# 3.3 混合参数
'''
可以把上面两种参数混合着使用. 也就是说在调用函数的时候即可以给出位置参数, 也可以指定关键字参数.
注意: 在使用混合参数的时候, 关键字参数必须在位置参数后面
'''

# 正确.第一个位置赋值给chat, 后面的参数开始指定关键字.
yue("微信", age=18, address="上海")

# 错误, 最开始使用了关键字参数, 那么后面的 微信的位置就串了, 容易出现混乱 这个要理解
# yue(age="18", "微信", address="广州")


'''
 综上: 在实参的⾓角度来看. 分为三种:
    - 位置参数
    - 关键字参数
    - 混合参数, 位置参数必须在关键字参数前面
位置参数:
　　- 位置参数,按照位置来赋值,到目前为止,我们编写的函数都是这种


        def yue(chat, address, age):   
        
            print("拿出手机")   
        
            print("打开"+chat)   
        
            print("找个"+address+"附近漂亮的"+str(age)+"岁妹子")   
        
            print("约不约")
 '''

# todo 3.4 默认值参数
'''
在函数声明的时候, 就可以给出函数参数的默认值. 在调用的时候可以 给出具体的值, 也可以不给值, 使⽤用默认值. 
比如, 我们录入咱们班学生的基本信息. 通过调查发现. 我们班大部分学生都是男生. 这个时候就可以给出⼀一个sex='男'的默认值.

注意:必须先声明在位置参数,才能声明关键字参数
综上:在形参的角度来看
    1、位置参数
    2、默认值参数(大多数传进来的参数都是一样的, 一般用默认参数
'''


def stu_info(name, age, sex='男'):
    print("录入学生信息")

    print(name, age, sex)

    print("录入完毕")


stu_info("张强", 18)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


#  todo 4、动态参数

'''
之前我们说过传参,如果我们在传参数的时候不很清楚有哪些的时候,或者说给一个函数传了很多参数,我们就要写很多,很麻烦怎么办呢,我们可以考虑使用动态参数

形参的第三种:动态参数
'''

#todo 4.1 动态接收位置参数

'''使用 *args 传递任意数量的位置参数，得到的结果是一个元组，可以像访问普通元组中的元素一样访问 *args 中的元素。'''

def eat(*args):

    print('我想吃',args)

eat('大米饭','中米饭','小米饭','燕麦','小麦')  # 收到的结果是一个tuple元祖


'''动态接收参数的时候要注意: 动态参数必须在位置参数后面,不然报错：

    def eat(*args,a,b):

    print('我想吃',args,a,b)
    
# eat函数在调用的时候发现缺少俩个位置参数没有进行传递
eat('大米饭','中米饭','小米饭')

 结果【报错】TypeError: eat() missing 2 required keyword-only arguments: 'a' and 'b'

原因就是因为这个在搞鬼 \把所有的位置参数都给接受了,所有会报错.我们尝试着把a,b放在*的前面试试，看下面：
'''

def eat(a,b,*args):

    print('我想吃',args,a,b)

eat('大米饭','中米饭','小米饭')


# todo 4.2、动态参数和默认参数

'''先看一个例子，
    本意是想默认值参数生效，输出 豆腐，粉条 白菜，（'猪肉'，'大葱'）
    实际上会是，豆腐 粉条，猪肉，（'大葱'）
    发现默认值参数写在动态参数前面是不生效,其实说白了，你只要不给默认值传参，那么它的默认参数就有值
注意: 形参的顺序: 位置参数 , 动态参数 , 默认参数
'''

# 默认参数不生效
def eat(a,b,c='白菜',*args):

    print('我想吃',a,b,c,args)

eat('豆腐','粉条','猪肉','大葱')


# 给默认值不传值，默认参数生效

def eat(a,b,*args,c='白菜'):

    print('我想吃',a,b,c,args)

eat('豆腐','粉条','猪肉','大葱')


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 4.3、动态接收关键字参数

'''在python中可以动态的位置参数,但是这种情况只能接收位置参数无法接收关键字参数,在python中使用 **kwargs 传递任意数量的关键字参数,参数在函数内部被接收为一个字典'''


def func_with_kwargs(**kwargs):
    for key, value in kwargs.items():
        print(f"{key} = {value}")

# 调用函数并传递任意数量的关键字参数
func_with_kwargs(name="Alice", age=30, city="New York")


# 动态参数混用时的顺序问题

'''顺序的问题, 在函数调用的时候, 如果先给出关键字参数, 则整个参数列表会报错.'''



def func(a, b, c, d):
    print(a, b, c, d)

# 这个调用会报错，关键字参数必须要放在位置参数后面，由于实参是这个顺序,所以形参接收的时候也是这个顺序.
# func(1, 2, c=3, 4)
# 也就是说位置参数必须在关键字参数前面.动态接收关键字参数也要在后面


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 练习 看几个混用的例子

# 如果想接收所有的参数:
def func(*args,**kwargs):

    print(args,kwargs)

func(1,23,5,a=1,b=6)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 动态参数还可以这样传参:

lst = [1,4,7]

# 方法一

def func(*args):

    print(args)

func(lst[0],lst[1],lst[2])

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# 方法二

def func(*args):

    print(args)

func(*lst)
# 在实参的位置上用*将lst(可迭代对象)按照顺序打散

# 在形参的位置上用*把收到的参数组合成一个元祖



# 字典也可以进行打散,不过需要**

dic = {'a':1,'b':2}

def func(**kwargs):

    print(kwargs)

func(**dic)


# 参数的组合使用

'''最终顺序:位置参数 > args(动态位置参数) > 默认值参数 > *kwargs(动态默认参数)'''
# 这里的话默认值不起作用，会被按顺序赋值
def example(pos1, pos2, default1=10, *args, kw1, kw2=20, **kwargs):
    print(f"pos1: {pos1}, pos2: {pos2}")
    print(f"default1: {default1}")
    print(f"args: {args}")
    print(f"kw1: {kw1}, kw2: {kw2}")
    print(f"kwargs: {kwargs}")


# Python3 允许关键字参数和默认参数可以跟在 *args 之后，并且这些参数必须以关键字方式传递
# def example(pos1, pos2, *args, default1=10, kw1, kw2=20, **kwargs):
#     print(pos1, pos2, args, default1, kw1, kw2)

# 调用函数
example(1, 2, 30, 40, 50, kw1="hello", kw3="world")
